/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package simhya.simengine.stochastic.jep;
import simhya.simengine.stochastic.NextEvent;
import simhya.utils.RandomGenerator;
import simhya.utils.Debugger;
import simhya.model.stochastic.*;
import simhya.simengine.stochastic.StochasticSimulator;
import simhya.simengine.utils.*;
import org.nfunk.jep.Variable;
import simhya.model.store.*;
import simhya.dataprocessing.DataCollector;
import simhya.simengine.*;
import simhya.model.transition.*;
import cern.jet.random.engine.RandomEngine;

/**
 *
 * @author Luca
 */
public abstract class StochasticSimulatorJep implements StochasticSimulator {
    DataCollector collector;
    StochasticModel model;
    boolean initialized;


    int numberOfTransitions;
    Variable[] variables;
    Variable[] parameters;

    String[] events;
    TType[] types;
    Predicate[] guards;
    Function[] rates;
    AtomicReset[][] resets;
    Function[] firingTimes;
    Function[] delays;
    Predicate[] delayedGuards;
    AtomicReset[][] delayedResets;
    boolean[] stopping;
    boolean[] stoppingAfterDelay;

    double initialTime;
    double currentTime;
    double finalTime;

    boolean [] currentGuardStatus;

    double lastSimulationTime;
    long lastSimulationStochasticSteps;
    long lastSimulationInstantaneousSteps;
    long lastSimulationTimedSteps;
    long lastSimulationDelayedSteps;

    RandomEngine rand;
    NextEvent next;

    public StochasticSimulatorJep(StochasticModel model, DataCollector collector) {
        this.collector = collector;
        this.model = model;

        this.initialized = false;
        this.initialTime = 0;
        this.finalTime = 0;
        this.currentTime  = 0;
        rand = RandomGenerator.getNewRandomGenerator();
        this.lastSimulationTime = 0;
        this.lastSimulationStochasticSteps = 0;
        this.lastSimulationInstantaneousSteps = 0;
        this.lastSimulationTimedSteps = 0;
        this.lastSimulationDelayedSteps = 0;
        next = null;

        this.initModelInfo();

    }

    private void initModelInfo() {
        variables = (Variable[])model.getStore().getVariables();
        parameters = (Variable[])model.getStore().getParameters();

        numberOfTransitions = model.getNumberOfTransitions();
        events = model.getTransitionEvents();
        types = model.getTransitionTypes();
        guards = model.getTransitionGuards();
        rates = model.getTransitionRates();
        resets = model.getTransitionResets();
        firingTimes = model.getTransitionTimedActivations();
        delays = model.getTransitionDelays();
        delayedGuards = model.getTransitionDelayedGuards();
        delayedResets = model.getTransitionDelayedResets();
        stopping = model.getTransitionStoppingStatus();
        stoppingAfterDelay = model.getTransitionStoppingStatusAfterDelay();
        currentGuardStatus = new boolean[numberOfTransitions];
    }


   
  
    
    public void setInitialTime(double time) {
//        if (this.initialized)
//            throw new SimulationException("Cannot change initial time: simualtor already initialized");
        this.initialTime = time;
    }
    
    public void setFinalTime(double time) {
//        if (this.initialized)
//            throw new SimulationException("Cannot change final time: simualtor already initialized");
        this.finalTime = time;
    }


    /**
     * resets the model and the variables/parameters to the initial state.
     */
    void resetModel() {
        model.resetToInitialState();
        variables = (Variable[])model.getStore().getVariables();
        parameters = (Variable[])model.getStore().getParameters();
    }
   

    /**
     * applies the reset of transition id
     * @param id
     */
    void execute(int id) {
        for (int j=0;j<resets[id].length;j++)
            resets[id][j].newValue = resets[id][j].resetFunction.compute();
        for (int j=0;j<resets[id].length;j++)
            variables[resets[id][j].variable].setValue(resets[id][j].newValue);
    }

  
    /**
     * applies the delayed reset of transition id, only if guard is true
     * @param id
     */
    void delayedExecute(int id) {
        if (delayedGuards[id].evaluate()) {
            for (int j=0;j<delayedResets[id].length;j++)
                delayedResets[id][j].newValue = delayedResets[id][j].resetFunction.compute();
            for (int j=0;j<delayedResets[id].length;j++)
                variables[delayedResets[id][j].variable].setValue(delayedResets[id][j].newValue);
        }
    }


    public void run() {
        next = new NextEvent();
        long simTime = System.nanoTime();
        this.lastSimulationStochasticSteps = 0;
        this.lastSimulationInstantaneousSteps = 0;
        this.lastSimulationTimedSteps = 0;
        this.lastSimulationDelayedSteps = 0;

        //Debugger.put("Simulation started");
        currentTime = this.initialTime;
        collector.putInitialState(model.getVariablesValues(), currentTime);

        int percentage = 1;

        while (true) {
            if(currentTime > finalTime*percentage/100)
                Debugger.put("Completed " + (percentage++) + "% in time " + ((double)System.nanoTime() - simTime)/1000000);
            //identify next transition
            selectNextTransition();
            //print + check termination
            //termination by end time reached
            if (next.nextTime > this.finalTime) {
                double[] v = model.getVariablesValues();
                if (collector.dataNeeded(this.finalTime))
                    collector.putData(v, this.finalTime);
                collector.putFinalState(v, finalTime);
                break;
            }
            if (collector.dataNeeded(next.nextTime)) {
                double[]  v = model.getVariablesValues();
                collector.putData(v, next.nextTime);
            }
            //update time
            currentTime = next.nextTime;
            //update model
            executeNextTransition();
            updateModel();
            //termination by forced stop
            if (next.stop) {
                double[] v = model.getVariablesValues();
                collector.putFinalState(v, currentTime);
                break;
            }
        }
        this.lastSimulationTime = (double)(System.nanoTime() - simTime)/1000000;
    }



    public abstract void initialize();
    public abstract void reset(DataCollector dc);
    abstract void selectNextTransition();
    abstract void executeNextTransition();
    abstract void updateModel();



    public void setProgressMonitor(ProgressMonitor monitor) {
        throw new RuntimeException("To be implemented");
    }
    public double getLastSimulationTime() {
        return this.lastSimulationTime;
    }
    public long getLastSimulationSteps() {
        return this.lastSimulationDelayedSteps + this.lastSimulationInstantaneousSteps +
                this.lastSimulationStochasticSteps + this.lastSimulationTimedSteps;
    }
    public long getLastSimulationDelayedSteps() {
        return lastSimulationDelayedSteps;
    }
    public long getLastSimulationInstantaneousSteps() {
        return lastSimulationInstantaneousSteps;
    }
    public long getLastSimulationStochasticSteps() {
        return lastSimulationStochasticSteps;
    }
    public long getLastSimulationTimedSteps() {
        return lastSimulationTimedSteps;
    }

}
